为了激发表达式模板的灵感，先从一种简单的方法开始，来实现支持数字数组操作的模板。基本数组模板可能如下所示(SArray代表简单数组):

\hspace*{\fill} \\ %插入空行
\noindent
\textit{exprtmpl/sarray1.hpp}
\begin{lstlisting}[style=styleCXX]
#include <cstddef>
#include <cassert>

template<typename T>
class SArray {
	public:
	// create array with initial size
	explicit SArray (std::size_t s)
	: storage(new T[s]), storage_size(s) {
		init();
	}

	// copy constructor
	SArray (SArray<T> const& orig)
	: storage(new T[orig.size()]), storage_size(orig.size()) {
		copy(orig);
	}

	// destructor: free memory
	~SArray() {
		delete[] storage;
	}

	// assignment operator
	SArray<T>& operator= (SArray<T> const& orig) {
		if (&orig!=this) {
			copy(orig);
		}
		return *this;
	}

	// return size
	std::size_t size() const {
		return storage_size;
	}

	// index operator for constants and variables
	T const& operator[] (std::size_t idx) const {
		return storage[idx];
	}
	T& operator[] (std::size_t idx) {
		return storage[idx];
	}

	protected:
	// init values with default constructor
	void init() {
		for (std::size_t idx = 0; idx<size(); ++idx) {
			storage[idx] = T();
		}
	}

	// copy values of another array
	void copy (SArray<T> const& orig) {
		assert(size()==orig.size());
		for (std::size_t idx = 0; idx<size(); ++idx) {
			storage[idx] = orig.storage[idx];
		}
	}

	private:
	T* storage; // storage of the elements
	std::size_t storage_size; // number of elements
};
\end{lstlisting}

数字运算符的代码如下:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{exprtmpl/sarrayops1.hpp}
\begin{lstlisting}[style=styleCXX]
// addition of two SArrays
template<typename T>
SArray<T> operator+ (SArray<T> const& a, SArray<T> const& b)
{
	assert(a.size()==b.size());
	SArray<T> result(a.size());
	for (std::size_t k = 0; k<a.size(); ++k) {
		result[k] = a[k]+b[k];
	}
	return result;
}

// multiplication of two SArrays
template<typename T>
SArray<T> operator* (SArray<T> const& a, SArray<T> const& b)
{
	assert(a.size()==b.size());
	SArray<T> result(a.size());
	for (std::size_t k = 0; k<a.size(); ++k) {
		result[k] = a[k]*b[k];
	}
	return result;
}

// multiplication of scalar and SArray
template<typename T>
SArray<T> operator* (T const& s, SArray<T> const& a)
{
	SArray<T> result(a.size());
	for (std::size_t k = 0; k<a.size(); ++k) {
		result[k] = s*a[k];
	}
	return result;
}

// multiplication of SArray and scalar
// addition of scalar and SArray
// addition of SArray and scalar
...
\end{lstlisting}

这些操作符和其他操作符的许多其他版本都可以这样写，这些足够我们的示例表达式使用:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{exprtmpl/sarray1.cpp}
\begin{lstlisting}[style=styleCXX]
#include "sarray1.hpp"
#include "sarrayops1.hpp"

int main()
{
	SArray<double> x(1000), y(1000);
	...
	x = 1.2*x + x*y;
}
\end{lstlisting}

由于两个原因，这个实现非常低效:

\begin{itemize}
\item 
操作符的每个应用程序(赋值除外)至少创建一个临时数组(即，假设编译器执行所有允许的临时复制消除操作，则在本例中至少创建三个大小为1,000的临时数组)。

\item 
操作符的每个应用程序都需要对参数数组和结果数组进行遍历(假设只生成三个临时SArray对象，在示例中将读取大约6000个double，并写入大约4000个double)。
\end{itemize}

具体的是一个使用临时变量操作的循环序列:

\begin{lstlisting}[style=styleCXX]
hat happens concretely is a sequence of loops that operates with temporaries:
tmp1 = 1.2*x; // loop of 1,000 operations
			  // plus creation and destruction of tmp1
tmp2 = x*y; // loop of 1,000 operations
		  // plus creation and destruction of tmp2
tmp3 = tmp1+tmp2; // loop of 1,000 operations
				  // plus creation and destruction of tmp3
x = tmp3; // 1,000 read operations and 1,000 write operations
\end{lstlisting}

除非使用特殊的快速分配器，否则创建不需要的临时变量通常占用小数组操作所需的时间。因为没有存储它们的储空间，所以对于真正的大型临时数组完全不可接受(具有挑战性的数值模拟通常试图使用所有可用的内存来获得更真实的结果。若内存用来保存不需要的临时文件，模拟的质量就会受到影响)。

数字数组库的早期实现也面临这个问题，并鼓励用户使用计算赋值(如+=、*=等)。这些赋值的优点是参数和目的地都是由调用方提供的，因此不需要临时变量。可以这样添加SArray成员:

\hspace*{\fill} \\ %插入空行
\noindent
\textit{exprtmpl/sarrayops2.hpp}
\begin{lstlisting}[style=styleCXX]
// additive assignment of SArray
template<typename T>
SArray<T>& SArray<T>::operator+= (SArray<T> const& b)
{
	assert(size()==orig.size());
	for (std::size_t k = 0; k<size(); ++k) {
		(*this)[k] += b[k];
	}
	return *this;
}

// multiplicative assignment of SArray
template<typename T>
SArray<T>& SArray<T>::operator*= (SArray<T> const& b)
{
	assert(size()==orig.size());
	for (std::size_t k = 0; k<size(); ++k) {
		(*this)[k] *= b[k];
	}
	return *this;
}

// multiplicative assignment of scalar
template<typename T>
SArray<T>& SArray<T>::operator*= (T const& s)
{
	for (std::size_t k = 0; k<size(); ++k) {
		(*this)[k] *= s;
	}
	return *this;
}
\end{lstlisting}

使用这样的运算符，计算示例可以重写为

\hspace*{\fill} \\ %插入空行
\noindent
\textit{exprtmpl/sarray2.cpp}
\begin{lstlisting}[style=styleCXX]
#include "sarray2.hpp"
#include "sarrayops1.hpp"
#include "sarrayops2.hpp"

int main()
{
	SArray<double> x(1000), y(1000);
	...
	// process x = 1.2*x + x*y
	SArray<double> tmp(x);
	tmp *= y;
	x *= 1.2;
	x += tmp;
}
\end{lstlisting}

显然，计算赋值的技术仍有不足:

\begin{itemize}
\item 
符号很笨拙。

\item 
不需要的临时tmp。

\item 
循环拆分为多个操作，总共需要从内存中读取大约6000个double元素，并将4000个double元素写入内存。
\end{itemize}

我们真正想要的是一个“理想循环”，可以处理每个索引的整个表达式:

\begin{lstlisting}[style=styleCXX]
int main()
{
	SArray<double> x(1000), y(1000);
	...
	for (int idx = 0; idx<x.size(); ++idx) {
		x[idx] = 1.2*x[idx] + x[idx]*y[idx];
	}
}
\end{lstlisting}

现在不需要临时数组，每次迭代只需要两次内存读取(x[idx]和y[idx])和一次内存写入(x[k])。因此，手动循环只需要大约2000次内存读取和1000次内存写入。

考虑到在现代高性能计算机架构中，内存带宽是这类数组操作速度的限制因素，这里展示的简单运算符重载方法的性能比手动编码循环慢一到两个数量级也不足为奇。但我们希望获得手动编码循环的性能，而不需要手工编写这些循环，也不需要使用笨拙的表示法，但这样做既麻烦又容易出错。















